#ifndef PERLINCUH
#define PERLINCUH
#include"raytracingtool.cuh"
__device__ inline float trilinear_interp(float c[2][2][2], float u, float v, float w) 
{
    u = u*u*u*(6*u*u-15*u+10);
    v = v*v*v*(6*v*v-15*v+10);
    w = w*w*w*(6*w*w-15*w+10);
    auto accum = 0.0;
    for (int i=0; i < 2; i++)
        for (int j=0; j < 2; j++)
            for (int k=0; k < 2; k++)
                accum += (i*u + (1-i)*(1-u))*
                        (j*v + (1-j)*(1-v))*
                        (k*w + (1-k)*(1-w))*c[i][j][k];

    return accum;
}
class perlin {
public:
    __device__ perlin(curandState *local_rand_state) 
    {
        ranfloat = new float[point_count];
        for (int i = 0; i < point_count; ++i) {
            ranfloat[i] = curand_uniform(local_rand_state);
    }

        perm_x = perlin_generate_perm(local_rand_state);
        perm_y = perlin_generate_perm(local_rand_state);
        perm_z = perlin_generate_perm(local_rand_state);
    }

    __device__ ~perlin() {
        delete[] ranfloat;
        delete[] perm_x;
        delete[] perm_y;
        delete[] perm_z;
    }
    
    __device__ float noise(const vec3& p) const {
        auto u = p.x() - floor(p.x());
        auto v = p.y() - floor(p.y());
        auto w = p.z() - floor(p.z());
        //6t^5-15t^4+10t^3
        //cudaChannelFormatDesc channelDesc=cudaCreateChannelDesc<uchar3>();
        int i = floor(p.x());
        int j = floor(p.y());
        int k = floor(p.z());
        float c[2][2][2];

        for (int di=0; di < 2; di++)
            for (int dj=0; dj < 2; dj++)
                for (int dk=0; dk < 2; dk++)
                    c[di][dj][dk] = ranfloat[
                        perm_x[(i+di) & 255] ^
                        perm_y[(j+dj) & 255] ^
                        perm_z[(k+dk) & 255]
                    ];

        return trilinear_interp(c, u, v, w);
    }

private:
    static const int point_count = 256;
    float* ranfloat;
    int* perm_x;
    int* perm_y;
    int* perm_z;

    __device__ static int* perlin_generate_perm(curandState *local_rand_state) {
        auto p = new int[point_count];

        for (int i = 0; i < perlin::point_count; i++)
            p[i] = i;

        permute(p, point_count,local_rand_state);

        return p;
    }

    __device__ static void permute(int* p, int n,curandState *local_rand_state) 
    {
        for (int i = n-1; i > 0; i--) 
        {
            int target = rand_int(0,i,local_rand_state);
            int tmp = p[i];
            p[i] = p[target];
            p[target] = tmp;
        }
    }
};

#endif